/*
 * ResultMessageCatalog.java
 *
 * $Id: ResultMessageCatalog.java,v 1.3 2008-06-26 10:42:17 mario Exp $
 */
package org.ceteca.explica.core.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.log4j.Logger;
import org.xnap.commons.i18n.I18n;
import org.xnap.commons.i18n.I18nFactory;

/**
 * Holds the catalog of available result messages.
 * <br/>
 * Responsibilities:
 * <ul>
 * 	<li> Store all the messages available. </li>
 *  <li> Get messages from the messages resource file. </li>
 * </ul>
 * <br/>
 * @author Mario García García <mario@imagos.es>
 * Copyright (c) 2007 Fundación Centro Tecnolóxico da Carne
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * <br/>
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * <br/>
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
public class ResultMessageCatalog {
	private static final long serialVersionUID = 10000011;

	/**
	 * Hashtable with the catalog of messages.
	 */
	private Properties messages;
	/**
	 * Path to the messages resource file.
	 */
	private String resourcesFile;
	/**
	 * Instance of internationalization used for localizing messages.
	 */
	private I18n i18n;
	/**
	 * Instance of logger used for writing log messages.
	 */
	private Logger logger;
	
	/**
	 * Instance of an upper level ResultMessage catalog so that the child modules
	 * can access to the common messages provided for the upper layers. The
	 * ResultMessageCatalogs can be organized into a hierarchy.
	 */
	private ResultMessageCatalog parent;

	/**
	 * Message text for an error locating the messages resource file from which the catalog
	 * is being built. 
	 */
	private static final String ERROR_RESOURCE_FILE_NOT_EXIST = "Error! The messages resource file (%s) doesn't exist or cannot be opened!";
	/**
	 * Message text for an error with the format of the messages resource file from which the catalog
	 * is being built. 
	 */
	private static final String ERROR_RESOURCE_FILE_NOT_VALID = "Error! The messages resource file (%s) doesn't have a valid format!";

	/**
	 * Default constructor for ResultMessageCatalog. Inits and builds an appropriate 
	 * result message catalog for the module. 
	 * @param resourcesFile String, path to the messages resources file.
	 */
	public ResultMessageCatalog(String resourcesFile) {
		initResultMessageCatalog(resourcesFile, null, null);
	}
	
	/**
	 * Constructor for ResultMessageCatalog. Inits and builds an appropriate 
	 * result message catalog for the module. 
	 * @param isResourcesFile InputStream, input stream opened for reading
	 * the messages resources file.
	 */
	public ResultMessageCatalog(InputStream isResourcesFile) {
		initResultMessageCatalog(isResourcesFile, null, null);
	}
	
	/**
	 * Constructor for ResultMessageCatalog. Inits and builds an appropriate 
	 * result message catalog for the module. 
	 * @param resourcesFile String, path to the messages resources file.
	 * @param i18n I18n, instance of internationalization used for localizing messages.
	 */
	public ResultMessageCatalog(String resourcesFile, I18n i18n) {
		initResultMessageCatalog(resourcesFile, i18n, null);
	}
	
	/**
	 * Constructor for ResultMessageCatalog. Inits and builds an appropriate 
	 * result message catalog for the module. 
	 * @param isResourcesFile InputStream, input stream opened for reading
	 * the messages resources file.
	 * @param i18n I18n, instance of internationalization used for localizing messages.
	 */
	public ResultMessageCatalog(InputStream isResourcesFile, I18n i18n) {
		initResultMessageCatalog(isResourcesFile, i18n, null);
	}
	
	/**
	 * Constructor for ResultMessageCatalog. Inits and builds an appropriate 
	 * result message catalog for the module. 
	 * @param resourcesFile String, path to the messages resources file.
	 * @param i18n I18n, instance of internationalization used for localizing messages.
	 * @param logger Logger, logger instance used for the logging of the module.
	 */
	public ResultMessageCatalog(String resourcesFile, I18n i18n, Logger logger) {
		initResultMessageCatalog(resourcesFile, i18n, logger);
	}
	
	/**
	 * Constructor for ResultMessageCatalog. Inits and builds an appropriate 
	 * result message catalog for the module. 
	 * @param isResourcesFile InputStream, input stream opened for reading
	 * the messages resources file.
	 * @param i18n I18n, instance of internationalization used for localizing messages.
	 * @param logger Logger, logger instance used for the logging of the module.
	 */
	public ResultMessageCatalog(InputStream isResourcesFile, I18n i18n, Logger logger) {
		initResultMessageCatalog(isResourcesFile, i18n, logger);
	}
	
	/**
	 * Inits and builds an appropriate result message catalog for the module. 
	 * @param resourcesFile String, path to the messages resources file.
	 * @param i18n I18n, instance of internationalization used for localizing messages.
	 * @param logger Logger, logger instance used for the logging of the module.
	 */
	private void initResultMessageCatalog(String resourcesFile, I18n i18n, Logger logger) {
		System.out.println(String.format("New ResultMessageCatalog(%s, %s, %s)", resourcesFile, i18n, logger));
		this.setResourcesFile(resourcesFile);
	
		// Open the resources file
		File f = new File(this.resourcesFile);
		FileInputStream fis = null;
		try {
			if (!f.exists()) {
				if (logger != null) {
					logger.error(String.format(this.ERROR_RESOURCE_FILE_NOT_EXIST, resourcesFile));
				}
				else {
					System.out.println(String.format(this.ERROR_RESOURCE_FILE_NOT_EXIST, resourcesFile));
				}
			}
			else {
				fis = new FileInputStream(f);
			}
		}
		catch (Exception ex) {
			if (logger != null) {
				logger.error(String.format(this.ERROR_RESOURCE_FILE_NOT_EXIST, resourcesFile), ex);
			}
			else {
				System.out.println(String.format(this.ERROR_RESOURCE_FILE_NOT_EXIST, resourcesFile));
				ex.printStackTrace();
			}
		}

		this.initResultMessageCatalog(fis, i18n, logger);
	}
	
	/**
	 * Inits and builds an appropriate result message catalog for the module. 
	 * @param isResourcesFile InputStream, input stream opened for reading
	 * the messages resources file.
	 * @param i18n I18n, instance of internationalization used for localizing messages.
	 * @param logger Logger, logger instance used for the logging of the module.
	 */
	private void initResultMessageCatalog(InputStream isResourcesFile, I18n i18n, Logger logger) {
		this.logger = logger;
		System.out.println(String.format("New ResultMessageCatalog(%s, %s, %s)", isResourcesFile, i18n, logger));
		if (i18n != null) {
			this.setI18n(i18n);
		}
		else {
			this.setI18n(I18nFactory.getI18n(this.getClass()));
		}
		
		// Open the resources file
		if (isResourcesFile == null) {
			if (logger != null) {
				logger.error(String.format(this.ERROR_RESOURCE_FILE_NOT_EXIST, resourcesFile));
			}
			else {
				System.out.println(String.format(this.ERROR_RESOURCE_FILE_NOT_EXIST, resourcesFile));
			}
		}
	
		// Read all the messages stored in the resources file and build the catalog.
		this.messages = new Properties();
		try {
			this.messages.load(isResourcesFile);
		}
		catch (IOException ioEx) {
			if (logger != null) {
				logger.error(String.format(this.ERROR_RESOURCE_FILE_NOT_VALID, resourcesFile), ioEx);
			}
			else {
				System.out.println(String.format(this.ERROR_RESOURCE_FILE_NOT_VALID, resourcesFile));
				ioEx.printStackTrace();
			}
		}
		
		this.setParent(null);
	}
	
	/**
	 * Gets the message resource file. 
	 * @return String, path to the messages resource file.
	 */
	public String getResourcesFile() {
		return resourcesFile;
	}
	/**
	 * Sets up the message resource file. 
	 * @param resourcesFile String, path to the messages resource file.
	 */
	public void setResourcesFile(String resourcesFile) {
		this.resourcesFile = resourcesFile;
	}

	/**
	 * Gets the internationalization object used for localizing messages. 
	 * @return I18n, internationalization object.
	 */
	public I18n getI18n() {
		return i18n;
	}
	/**
	 * Sets up the internationalization object used for localizing messages. 
	 * @param I18n i18n, internationalization object.
	 */
	public void setI18n(I18n i18n) {
		this.i18n = i18n;
	}
	
	/**
	 * Gets the parent messages catalog. 
	 * @return ResultMessageCatalog, parent messages catalog.
	 */
	public ResultMessageCatalog getParent() {
		return parent;
	}
	/**
	 * Sets up the parent messages catalog. 
	 * @param parent ResultMessageCatalog, parent messages catalog.
	 */
	public void setParent(ResultMessageCatalog parent) {
		this.parent = parent;
	}

	/**
	 * Returns the message string defined for the result code. If
	 * the message code cannot be found in this catalog the code is
	 * searched in the parent catalogs.
	 * @param resultCode int, result code.
	 * @return String, message literal defined for the result code.
	 */
	public String getMessage(int resultCode) {
		String msg = null;
		msg = this.messages.getProperty("" + resultCode);
		if ((msg == null || msg.length() <= 0) && this.parent != null)
			return this.parent.getMessage(resultCode);

		return this.i18n.tr(msg);
	}
	
	/**
	 * Creates a new ResultMessage appropriate for the result code. 
	 * @param resCode int, result message internal code.
	 * @return ResultMessage, the result message created.
	 */
	public ResultMessage newResultMessage(int resCode) {
		return buildResultMessage(resCode, null, null, null);
	}
	
	/**
	 * Creates a new ResultMessage appropriate for the result code, parametrizing 
	 * the message with the right values. It's a shortcut for
	 * using only one or two parameters, without building a parameter list. 
	 * @param resCode int, result message internal code.
	 * @param param1 Object, first parameter values for the message.
	 * @param param2 Object, second parameter values for the message.
	 * @return ResultMessage, the result message created. 
	 */
	public ResultMessage newResultMessage(int resCode, Object param1, Object param2) {
		ArrayList lParams = new ArrayList();
		lParams.add(param1);
		if (param2 != null)
			lParams.add(param2);
		return buildResultMessage(resCode, null, lParams, null);
	}
	
	/**
	 * Creates a new ResultMessage appropriate for the result code, parametrizing
	 * the message with the right values. 
	 * @param resCode int, result message internal code.
	 * @param lParams ArrayList, list of the parameter values for the message.
	 * @return ResultMessage, the result message created. 
	 */
	public ResultMessage newResultMessage(int resCode, ArrayList lParams) {
		return buildResultMessage(resCode, null, lParams, null);
	}
	
	/**
	 * Creates a new ResultMessage appropriate for the result code, parametrizing 
	 * the message with the right values and identifying the cause of the message. 
	 * It also adds a form field linked to the result message. 
	 * @param resCode int, result message internal code.
	 * @param lParams ArrayList, list of the parameter values for the message.
	 * @param fieldName String, form field's name linked to the result message.
	 * @return ResultMessage, the result message created. 
	 */
	public ResultMessage newResultMessage(int resCode, ArrayList lParams, String fieldName) {
		return buildResultMessage(resCode, null, lParams, fieldName);
	}
	
	/**
	 * Creates a new ResultMessage appropriate for the result code, parametrizing 
	 * the message with the right values. It also adds a form field linked to the 
	 * result message. It's a shortcut for using only one or two parameters, 
	 * without building a parameter list. 
	 * @param resCode int, result message internal code.
	 * @param param1 Object, first parameter values for the message.
	 * @param param2 Object, second parameter values for the message.
	 * @param fieldName String, form field's name linked to the result message.
	 * @return ResultMessage, the result message created. 
	 */
	public ResultMessage newResultMessage(int resCode, Object param1, Object param2, String fieldName) {
		ArrayList lParams = new ArrayList();
		lParams.add(param1);
		if (param2 != null)
			lParams.add(param2);
		return buildResultMessage(resCode, null, lParams, fieldName);
	}
	
	/**
	 * Creates a new ResultMessage appropriate for the result code, identifying 
	 * the cause of the message. 
	 * @param resCode int, result message internal code.
	 * @param cause Throwable, exception that raised the result message. Only
	 * applies with error messages.
	 * @return ResultMessage, the result message created.
	 */
	public ResultMessage newResultMessage(int resCode, Throwable cause) {
		return buildResultMessage(resCode, cause, null, null);
	}
	
	/**
	 * Creates a new ResultMessage appropriate for the result code, parametrizing
	 * the message with the right values and identifying the cause of the message. 
	 * @param resCode int, result message internal code.
	 * @param cause Throwable, exception that raised the result message. Only
	 * applies with error messages.
	 * @param lParams ArrayList, list of the parameter values for the message.
	 * @return ResultMessage, the result message created. 
	 */
	public ResultMessage newResultMessage(int resCode, Throwable cause, ArrayList lParams) {
		return buildResultMessage(resCode, cause, lParams, null);
	}
	
	/**
	 * Creates a new ResultMessage appropriate for the result code, parametrizing 
	 * the message with the right values and identifying the cause of the message. 
	 * It's a shortcut for using only one or two parameters, without building a 
	 * parameter list. 
	 * @param resCode int, result message internal code.
	 * @param cause Throwable, exception that raised the result message. Only
	 * applies with error messages.
	 * @param param1 Object, first parameter values for the message.
	 * @param param2 Object, second parameter values for the message.
	 * @return ResultMessage, the result message created. 
	 */
	public ResultMessage newResultMessage(int resCode, Throwable cause, Object param1, Object param2) {
		ArrayList lParams = new ArrayList();
		lParams.add(param1);
		if (param2 != null)
			lParams.add(param2);
		return buildResultMessage(resCode, cause, lParams, null);
	}
	
	/**
	 * Creates a new ResultMessage appropriate for the result code, parametrizing 
	 * the message with the right values and identifying the cause of the message. 
	 * It also adds a form field linked to the result message. 
	 * @param resCode int, result message internal code.
	 * @param cause Throwable, exception that raised the result message. Only
	 * applies with error messages.
	 * @param lParams ArrayList, list of the parameter values for the message.
	 * @param fieldName String, form field's name linked to the result message.
	 * @return ResultMessage, the result message created. 
	 */
	public ResultMessage newResultMessage(int resCode, Throwable cause, ArrayList lParams, String fieldName) {
		return buildResultMessage(resCode, cause, lParams, fieldName);
	}
	
	/**
	 * Creates a new ResultMessage appropriate for the result code, parametrizing 
	 * the message with the right values and identifying the cause of the message. 
	 * It also adds a form field linked to the result message. It's a shortcut for
	 * using only one or two parameters, without building a parameter list. 
	 * @param resCode int, result message internal code.
	 * @param cause Throwable, exception that raised the result message. Only
	 * applies with error messages.
	 * @param param1 Object, first parameter values for the message.
	 * @param param2 Object, second parameter values for the message.
	 * @param fieldName String, form field's name linked to the result message.
	 * @return ResultMessage, the result message created. 
	 */
	public ResultMessage newResultMessage(int resCode, Throwable cause, Object param1, Object param2, String fieldName) {
		ArrayList lParams = new ArrayList();
		lParams.add(param1);
		if (param2 != null)
			lParams.add(param2);
		return buildResultMessage(resCode, cause, lParams, fieldName);
	}
	
	/**
	 * Builds a new Parametrized Error ResultMessage class. Builds an appropriate
	 * result message for the result code, parametrizing the message with
	 * the right values and identifying the cause of the message. It also adds
	 * a form field linked to the result message. 
	 * @param resCode int, result message internal code.
	 * @param cause Throwable, exception that raised the result message. Only
	 * applies with error messages.
	 * @param lParams ArrayList, list of the parameter values for the message.
	 * @param fieldName String, form field's name linked to the result message.
	 * @return ResultMessage, the result message created. 
	 */
	private ResultMessage buildResultMessage(int resCode, Throwable cause, ArrayList lParams, String fieldName) {
		ResultMessage resMessage = new ResultMessage(resCode, cause, lParams, fieldName);
		resMessage.setResMessage(this.getMessage(resCode));
		
		return resMessage;
	}
	
	
	/**
	 * Add a list of result messages to the catalog. The new result messages
	 * will be loaded from a different messages properties file
	 * @param isResourcesFile InputStream, stream opened to the new messages
	 * properties file.
	 */
	public void addResultMessages(InputStream isResourcesFile) {
		// Open the resources file
		if (isResourcesFile == null) {
			if (this.logger != null) {
				this.logger.error(String.format(ERROR_RESOURCE_FILE_NOT_EXIST, resourcesFile));
			}
			else {
				System.out.println(String.format(ERROR_RESOURCE_FILE_NOT_EXIST, resourcesFile));
			}
		}
		
		// Init the messages object
		if (this.messages == null)
			this.messages = new Properties();
	
		// Read all the messages stored in the resources file and add them to the catalog.
		try {
			if (isResourcesFile != null) {
				Properties p = new Properties();
				p.load(isResourcesFile);
				
				// Get the set of new loaded messages
				Set<Entry<Object, Object>> valuesSet = p.entrySet();
				if (valuesSet != null && valuesSet.size() > 0) {
					Iterator it = valuesSet.iterator();
					while (it != null && it.hasNext()) {
						// Add the message to the catalog
						Entry e = (Entry)it.next();
						this.messages.put(e.getKey(), e.getValue());
					} // while
				} // if there is any property loaded from the stream
			} // if resourcesFile != null
		} // try
		catch (IOException ioEx) {
			if (this.logger != null) {
				this.logger.error(String.format(ERROR_RESOURCE_FILE_NOT_VALID, resourcesFile), ioEx);
			}
			else {
				System.out.println(String.format(ERROR_RESOURCE_FILE_NOT_VALID, resourcesFile));
				ioEx.printStackTrace();
			}
		} // catch
	}
	
	/**
	 * Print the list of loaded messages available in the catalog
	 * to the standard console.
	 */
	public void printMessages() {
		this.messages.list(System.out);
	}
	
	/**
	 * Print the list of loaded messages available in the catalog
	 * to the indicated output stream.
	 */
	public void printMessages(PrintStream out) {
		this.messages.list(out);
	}
	
	public ResultMessage translateMessage(ResultMessage resMsg) {
		ResultMessage result = null;
		String msg = null;
		if (resMsg != null) {
			msg = this.messages.getProperty("" + resMsg.getResCode());
			if ((msg == null || msg.length() <= 0) && this.parent != null)
				result = this.parent.translateMessage(resMsg);
			else if (msg != null && msg.length() > 0) {
				
				// Localize the result message text
				if (resMsg.getLParams() != null && resMsg.getLParams().size() > 0)
					msg = this.i18n.tr(msg, resMsg.getLParams().toArray());
				else 
					msg = this.i18n.tr(msg);
				
				// Set the text in the returned result message
				result = new ResultMessage(resMsg.getResCode(), resMsg.getExc(), resMsg.getLParams(), resMsg.getFieldName());
				result.setResMessage(msg);
			}
			else
				result =  null;
		}
		else
			result = null;
		
		return result;
	}
	
	/**
	 * Returns tag Id assigned to CVS source file.
	 */
	public static String getRevision() {
		return "$Id: ResultMessageCatalog.java,v 1.3 2008-06-26 10:42:17 mario Exp $";
	}
}

